#include "../config.h"

#include <stdio.h>
#include <langinfo.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>

#include <queue>

#include "log.h"

namespace gpyp {
namespace log {

Glib::StaticMutex _static_mutex = GLIBMM_STATIC_MUTEX_INIT;

namespace {
int _log_fd = 0;

Glib::Dispatcher *_dispatcher_message = NULL;

size_t _message_queue_max_size = 20, _message_queue_size = 0;
MessageQueueEntry *_message_queue_first = NULL, *_message_queue_end = NULL;

sigc::signal<void, Logger::id_type, const Glib::ustring &> _signal_message;

std::string _log_dir;
int         _max_logs = 10;
}

//========================
// Logger
size_t Logger::_id = 0;

void Logger::prepare_buffer() {
  if (!buffer_) {
    buffer_ = new std::stringstream();

    static char   buf[64] = {0};
    static time_t last_time = 0;
    time_t _t = time(NULL);
    if (!buf[0] || _t != last_time) {
      last_time = _t;

      struct tm *_tm = localtime(&_t);
      if (_tm == NULL ||
          strftime(buf, sizeof(buf), "%m/%d %H:%M:%S", _tm) == 0) {
        strcpy(buf, "error!");
      }
    }
    char sig[] = {' ', sig_, ':', ' ', '\0'};
    *buffer_ << buf << sig;
  }
}

Logger &Logger::operator<<(Logger &(*)(Logger &)) {
  _static_mutex.lock();
  prepare_buffer();
  Glib::ustring *msg = new Glib::ustring(buffer_->str());
  try {
    out_ << *msg << std::endl;
  } catch(const Glib::ConvertError &/*ex*/) {
    out_ << buffer_->str() << std::endl;
  }
  if (_log_fd) {
    if (write(_log_fd, msg->c_str(), msg->bytes()) > 0 && write(_log_fd, "\n", 1) > 0) {
#ifdef __linux__
      fdatasync(_log_fd);
#else
      fsync(_log_fd);
#endif
    }
  }
  delete buffer_;
  buffer_ = NULL;

  MessageQueueEntry *e = new MessageQueueEntry(id_, msg);
  if (_message_queue_end) {
    _message_queue_end->next = e;
    _message_queue_end = e;
  } else {
    _message_queue_first = _message_queue_end = e;
  }
  ++_message_queue_size;
  while(_message_queue_size > _message_queue_max_size) {
    MessageQueueEntry *e = _message_queue_first;
    _message_queue_first = e->next;
    delete e;
    --_message_queue_size;
  }
  _dispatcher_message->emit();
  _static_mutex.unlock();
  return *this;
}

Logger::Logger(char sig, std::ostream &out) :
  id_    (++_id),
  sig_   (sig),
  out_   (out),
  buffer_(NULL)
{
}
Logger::Logger(const Logger &logger) :
  id_    (logger.id_),
  sig_   (logger.sig_),
  out_   (logger.out_),
  buffer_(NULL)
{
}
Logger::~Logger() {
  _static_mutex.lock();
  if (buffer_) {
    delete buffer_;
    buffer_ = NULL;
  }
  _static_mutex.unlock();
}

template<>
Logger &Logger::operator<<(const Glib::ustring &message) {
  _static_mutex.lock();
  prepare_buffer();
  *buffer_ << message.c_str();
  _static_mutex.unlock();
  return *this;
}

Logger log('L', std::cout);
Logger err('E', std::cerr);
Logger dbg('D', std::cout);

Logger &endl(Logger &l) {
  return l;
}

sigc::signal<void, Logger::id_type, const Glib::ustring &> signal_message() {
  return _signal_message;
}
MessageQueueEntry *get_messages() {
  return _message_queue_first;
}

void send_message() {
  _static_mutex.lock();
  for(MessageQueueEntry *e = _message_queue_first; e; e = e->next) {
    if (!e->sent) {
      _signal_message(e->id, *e->msg);
      e->sent = true;
    }
  }
  _static_mutex.unlock();
}

void init(const OptionContext &opts) {
  _log_dir  = Glib::build_filename(Glib::filename_from_utf8(opts.get_config_dir()), "log");
  _max_logs = opts.get_max_logs();

  if (_max_logs > 0 || _max_logs == -1) {
    time_t t = time(NULL);
    struct tm tmp;
    if (localtime_r(&t, &tmp)) {
      char log_filename[64];
      if (strftime(log_filename, 64, "%Y%m%d_%H%M%S.log", &tmp)) {
        std::string path = Glib::build_filename(_log_dir, log_filename);
        g_mkdir_with_parents(_log_dir.c_str(), 0700);
        _log_fd = open(path.c_str(), O_WRONLY | O_CREAT, 0644);
      }
    }
  }

  _dispatcher_message = new Glib::Dispatcher();
  _dispatcher_message->connect(sigc::ptr_fun(send_message));
}
void uninit() {
  _static_mutex.lock();
  while(_message_queue_first) {
    MessageQueueEntry *e = _message_queue_first;
    _message_queue_first = e->next;
    delete e;
  }
  if (_log_fd) {
    fsync(_log_fd);
    close(_log_fd);
    _log_fd = 0;
  }
  if (_max_logs >= 0) {
    try {
      Glib::Dir dir(_log_dir);
      std::list<std::string> logs;
      for(Glib::DirIterator iter = dir.begin(); iter != dir.end(); ++iter) {
        std::string path = *iter;
        if (path.size() > 4 &&
            path.compare(path.size() - 4, 4, ".log") == 0) {
          logs.push_back(path);
        }
      }
      if (logs.size() > static_cast<unsigned>(_max_logs)) {
        logs.sort();
        std::list<std::string>::iterator iter = logs.begin();
        for(size_t i = 0; i < logs.size() - _max_logs; ++i) {
          std::string path = Glib::build_filename(_log_dir, *iter);
          remove(path.c_str());
          ++iter;
        }
      }
    } catch(...) { }
  }
  _static_mutex.unlock();
  delete _dispatcher_message;
}

}/*log*/
}/*gpyp*/
